package com.balloon.springboot.logger.aspect;

import com.balloon.core.date.DateTimeUtils;
import com.balloon.springboot.logger.entity.LoggerInfo;
import com.balloon.springboot.logger.service.LoggerInfoService;
import com.balloon.springboot.redis.utils.RedisUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.bitwalker.useragentutils.UserAgent;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.balloon.springboot.orm.entity.InitBaseEntity;

import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;

@Aspect
@Component
public class WebLogAspect {

    private final static Logger log = LoggerFactory.getLogger(WebLogAspect.class);

    @Autowired
    private LoggerInfoService loggerInfoServiceImpl;

    @Autowired
    private RedisUtils redisUtils;

    public WebLogAspect() {
    }


    /**
     * 定义请求日志切入点，其切入点表达式有多种匹配方式,这里是指定路径
     * com.balloon.springboot.job.web
     */
    @Pointcut("execution( * com.balloon.springboot.*.web.*.*(..)) || execution( * com.balloon.springboot.*.*.web.*.*(..))")
    public void webLogPointcut() {
    }

    /**
     * 前置通知：
     * 1. 在执行目标方法之前执行，比如请求接口之前的登录验证;
     * 2. 在前置通知中设置请求日志信息，如开始时间，请求参数，注解内容等
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLogPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("token");
            //获取请求头中的User-Agent
            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
            //打印请求的内容
            String loggerId = String.valueOf(UUID.randomUUID());
            Long startTime = System.currentTimeMillis();
            String userInfoStr = redisUtils.get("SYSTEM_USER_INFO:" + token);

            // 处理没有登录时候日志记录报错问题
            String userId;
            String username;
            if(userInfoStr !=null){
                Map map = mapper.readValue(userInfoStr, Map.class);
                userId = map.get("id").toString();
                username = map.get("username").toString();
            }else {
                userId = "security-admin";
                username = "security-admin";
            }

            MDC.put("loggerId", loggerId);
            MDC.put("startTime", String.valueOf(startTime));
            LoggerInfo loggerInfo = new LoggerInfo(loggerId,
                    userId, username,
                    request.getRemoteAddr(),
                    userAgent.getOperatingSystem().toString(),
                    request.getRequestURL().toString(),
                    Arrays.toString(joinPoint.getArgs()),
                    request.getMethod(),
                    userAgent.getBrowser().toString(),
                    userAgent.getBrowserVersion().toString());
            InitBaseEntity.initDateTime(loggerInfo);
            loggerInfoServiceImpl.saveLoggerInfo(loggerInfo);
            log.info("请求开始时间：{}", LocalDateTime.now());
            log.info("请求Url : {}", request.getRequestURL().toString());
            log.info("请求方式 : {}", request.getMethod());
            log.info("请求ip : {}", request.getRemoteAddr());
            log.info("请求方法 : {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
            // 系统信息
            log.info("浏览器：{}", userAgent.getBrowser().toString());
            log.info("浏览器版本：{}", userAgent.getBrowserVersion());
            log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
        } catch (Exception e) {
            log.info("保存日志异常!");
            e.printStackTrace();
        }

    }

    /**
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLogPointcut()")
    public void doAfterReturning(Object ret) {

        try {
            long endTime = System.currentTimeMillis();
            long startTime = Long.parseLong(MDC.get("startTime"));
            log.info("请求结束时间：{}", LocalDateTime.now());
            log.info("请求耗时：{}", (endTime - startTime));
            // 处理完请求，返回内容
            log.info("请求返回 : {}", ret);
            String retStr;
            if (ret == null) {
                retStr = "请求成功";
            } else {
                retStr = ret.toString();
            }
            LoggerInfo loggerInfo = loggerInfoServiceImpl.findByLoggerInfoId(MDC.get("loggerId"));
            groupLoggerInfo(loggerInfo, startTime, endTime, "success", retStr);

        } catch (Exception e) {
            log.info("保存日志异常!");
            e.printStackTrace();
        }
    }

    /**
     * @param throwable 异常
     */
    @AfterThrowing(value = "webLogPointcut()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        // 保存异常日志记录
        LoggerInfo loggerInfo = loggerInfoServiceImpl.findByLoggerInfoId(MDC.get("loggerId"));
        long failDateTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        log.info("发生异常时间：{}", failDateTime);
        String resultVal = throwable.getMessage();
        long startTime = Long.parseLong(MDC.get("startTime"));
        groupLoggerInfo(loggerInfo, startTime, failDateTime, "fail", resultVal);

        log.info("抛出异常：{}", throwable.getMessage());
    }

    private void groupLoggerInfo(LoggerInfo loggerInfo, Long startTime, long now, String requestState, String resultVal) {

        loggerInfo.setRequestState(requestState);
        loggerInfo.setResultVal(resultVal);
        LocalDateTime startTimeOfEpoch = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault());
        LocalDateTime endTimeOfEpoch = LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault());
        loggerInfo.setRequestStartTime(DateTimeUtils.formatDateTime(startTimeOfEpoch, "yyyy-MM-dd hh:mm:ss"));
        loggerInfo.setRequestEndTime(DateTimeUtils.formatDateTime(endTimeOfEpoch, "yyyy-MM-dd hh:mm:ss"));
        loggerInfo.setConsumeTime(String.valueOf(now - startTime));
        loggerInfoServiceImpl.saveLoggerInfo(loggerInfo);

    }
}
